package edu.scnu.fans.fingertip.filter;

import com.alibaba.fastjson.JSON;
import edu.scnu.fans.fingertip.common.api.ApiResponse;
import edu.scnu.fans.fingertip.common.api.CommonConstant;
import edu.scnu.fans.fingertip.common.api.HeaderConstant;
import edu.scnu.fans.fingertip.config.AuthUrlConfig;
import edu.scnu.fans.fingertip.user.api.UserApi;
import edu.scnu.fans.fingertip.user.output.VerifyTokenOutput;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;

@Component
public class AuthFilter extends AbstractGatewayFilterFactory<Object> {

    @Resource
    private AuthUrlConfig authUrlConfig;

    @Resource
    private UserApi userApi;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public GatewayFilter apply(Object config) {
        return ((exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();
            HttpHeaders headers = request.getHeaders();
            ServerHttpRequest.Builder mutate = request.mutate();
            String requestURI = request.getPath().toString();

            // 手动设置用户ID，恶意请求
            if (headers.getFirst(HeaderConstant.USER_ID) != null) {
                return return401(response);
            }

            String token = headers.getFirst("token");
            Long userId = Optional.ofNullable(stringRedisTemplate.opsForValue().get(CommonConstant.TOKEN_REDIS_KEY + token))
                    .map(Long::valueOf)
                    .orElse(null);

            List<String> exclude = authUrlConfig.getExclude();
            AntPathMatcher matcher = new AntPathMatcher();

            boolean shouldExclude = exclude.stream().anyMatch(urlConfig -> matcher.match(urlConfig, requestURI));

            // 需要检验token 正确性的
            if (!shouldExclude) {

                // 没有 token
                if (StringUtils.isBlank(token)) {
                    return this.return401(response);
                }

                // token 无效
                if (userId == null) {
                    return this.return401(response);
                }

                // 验证 token 有效性
                ApiResponse<VerifyTokenOutput> verifyTokenResponse = userApi.verifyToken(userId, token);
                if (verifyTokenResponse.getCode() != HttpStatus.OK.value()) {
                    return this.return401(response);
                }
                VerifyTokenOutput verifyTokenResult = verifyTokenResponse.getData();
                if (!verifyTokenResult.isSuccess()) {
                    return this.return401(response);
                }
            }

            // 微服务内部设置消息头
            if (userId != null) {
                mutate.header(HeaderConstant.USER_ID, userId.toString());
            }

            return chain.filter(exchange.mutate()
                    .request(mutate.build())
                    .response(response)
                    .build());
        });
    }

    /**
     * 返回401
     */
    private Mono<Void> return401(ServerHttpResponse response) {
        // 恶意请求，抛出用户未授权
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
        DataBuffer buffer = response.bufferFactory().wrap(JSON.toJSONString(new ApiResponse<>(401, "token失效", new Object())).getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Flux.just(buffer));
    }
}
